#include <assert.h>

#include "net_context.h"
#include "ip4_impl.h"
#include "ifconfig.h"

using namespace usnet;

std::unique_ptr<IP4Impl>
IP4Impl::MakeUnique(const PPT proto, EndpointAddr* laddr, EndpointAddr* raddr)
{
    auto ctx = NetContext::GetContext();
    std::unique_ptr<IP4Impl> ip4_impl { new(std::nothrow) IP4Impl { proto, laddr, raddr } };
    if (ip4_impl == nullptr) {
        LOG_ERROR(ctx, "failed to new IP4Impl instance");
        return nullptr;
    }
    LOG_DEBUG(ctx, "new IP4Impl instance success");
    if (ip4_impl->InitARPImpl() == false) {
        LOG_ERROR(ctx, "failed to init ARPImpl");
        return nullptr;
    }
    LOG_DEBUG(ctx, "init ARPImpl success");
    if (ip4_impl->InitEthImpl() == false) {
        LOG_ERROR(ctx, "failed to init EthImpl");
        return nullptr;
    }
    LOG_DEBUG(ctx, "init EthImpl success");
    return ip4_impl;
}

std::shared_ptr<FrameBufMgr> IP4Impl::PrefillHeader()
{
    auto fb_mgr = eth_impl_->PrefillHeader();
    FrameBuf* fb { nullptr };
    LinkNode list_head { std::move(fb_mgr->tx_fb_free_) };
    while((fb = list_head.GetHead())) {
        IP4Header* ip4_hdr = (IP4Header*) ((char*)fb->eth_hdr_ + eth_impl_->GetHeaderSize());
        ip4_hdr->ipvhl_ = (static_cast<uint8_t>(InetProto::IPv4) | hdr_size_);
        ip4_hdr->tos_ = 0;
        ip4_hdr->be_off_ = 0;
        ip4_hdr->ttl_ = DEFAUL_TTL;
        ip4_hdr->protocol_ = static_cast<uint8_t>(proto_);
        ip4_hdr->be_laddr_ = laddr_->be_ip_.v4_;
        ip4_hdr->be_raddr_ = raddr_->be_ip_.v4_;
        fb->inet_hdr_.ip4_hdr_ = ip4_hdr;
        list_head.RemoveHead();
        fb_mgr->tx_fb_free_.TailInsert(fb);
    }

    list_head = std::move(fb_mgr->rx_fb_free_);
    while((fb = list_head.GetHead())) {
        IP4Header* ip4_hdr = (IP4Header*) ((char*)fb->eth_hdr_ + eth_impl_->GetHeaderSize());
        fb->inet_hdr_.ip4_hdr_ = ip4_hdr;
        list_head.RemoveHead();
        fb_mgr->rx_fb_free_.TailInsert(fb);
    }

    fb_mgr_ = fb_mgr;
    return fb_mgr_;
}

void IP4Impl::SendPacket()
{
    auto ctx = NetContext::GetContext();
    LinkNode list_head { std::move(fb_mgr_->tx_fb_fill_) };
    FrameBuf* fb { nullptr };
    while ((fb = list_head.GetHead())) {
        LOG_DEBUG(ctx, "filling IPv4 header");
        auto ip4_hdr = fb->inet_hdr_.ip4_hdr_;
        ip4_hdr->be_len_ = htons(fb->tsp_pdu_ + GetHeaderSize());
        ip4_hdr->be_pid_ = htons(pktid_++);
        ip4_hdr->be_checksum_ = 0;
        ip4_hdr->be_checksum_ = htons(CheckSum(fb));
        list_head.RemoveHead();
        fb_mgr_->tx_fb_fill_.TailInsert(fb);
    }
    return eth_impl_->SendFrame();
}

bool IP4Impl::RecvPacket()
{
    auto ctx = NetContext::GetContext();
    bool res { false };
    if (eth_impl_->RecvFrame() == false) {
        return res;
    }
    FrameBuf* fb { nullptr };
    while ((fb = fb_mgr_->rx_fb_fill_.GetHead())) {
        if (CheckSum(fb) != 0) {
            fb_mgr_->rx_fb_fill_.RemoveHead();
            fb_mgr_->rx_fb_free_.TailInsert(fb);
            fb = fb_mgr_->rx_fb_fill_.GetHead();
            LOG_DEBUG(ctx, "incorrect IPv4 checksum");
            continue;
        }
        res = true;
        auto ip4_hdr = fb->inet_hdr_.ip4_hdr_;
        assert(ip4_hdr);
        fb->tsp_pdu_ = ntohs(ip4_hdr->be_len_) - ((ip4_hdr->ipvhl_ & 0xf) << 2);
        fb_mgr_->rx_fb_fill_.RemoveHead();
        fb_mgr_->rx_fb_recv_.TailInsert(fb);
    }
    LOG_DEBUG(ctx, "IPv4 packet received");
    return res;
}